#ifndef AVALON_CONNECTIONSERVER_HPP
#define AVALON_CONNECTIONSERVER_HPP
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include "Avalon/Base/Algorithm.hpp"
#include "Avalon/IO/AcceptState.hpp"
#include "Avalon/IO/Connection.hpp"
#include "Avalon/IO/ConnectionState.hpp"
#include "Avalon/IO/ServerConnection.hpp"

namespace Avalon {
namespace IO {

  /*! \class ConnectionServer
      \brief Handles accepting and managing Channels for a ServerConnection.
      \tparam ChannelType The type of Channel to accept.
   */
  template<typename ChannelType>
  class ConnectionServer {
    public:

      //! Called when a Channel is accepted.
      /*!
        \param channel The Channel that was accepted.
      */
      typedef boost::function<void (ChannelType* channel)> AcceptSlot;

      //! Called when a Channel is closed.
      /*!
        \param channel The Channel that closed.
      */
      typedef boost::function<void (ChannelType* channel)> ChannelClosedSlot;

      //! Called when the ServerConnection is closed.
      typedef boost::function<void ()> ServerConnectionClosedSlot;

      //! Constructs a ConnectionServer.
      ConnectionServer();

      ~ConnectionServer();

      //! Initializes the ConnectionServer.
      /*!
        \param serverConnection The ServerConnection accepting Channels.
        \param acceptSlot The slot to call when a Channel is accepted.
        \param channelClosedSlot The slot to call when a Channel is closed.
        \param serverConnectionClosedSlot The slot to call when the
                                          ServerConnection is closed.
      */
      void Initialize(ServerConnection* serverConnection,
        const AcceptSlot& acceptSlot,
        const ChannelClosedSlot& channelClosedSlot,
        const ServerConnectionClosedSlot& serverConnectionClosedSlot);

      //! Returns <code>true</code> iff Channels are being accepted.
      bool IsOpen() const;

      //! Starts accepting Channels.
      void Open();

      //! Closes all Channels and stops accepting new ones.
      void Close();

      //! Removes a Channel once it has been closed.
      /*!
        \param channel The Channel to remove, must have been received through a
                       ChannelClosedSlot.
      */
      void RemoveChannel(ChannelType* channel);

    private:
      mutable boost::recursive_mutex m_mutex;
      boost::scoped_ptr<ServerConnection> m_serverConnection;
      ConnectionState m_serverConnectionState;
      AcceptSlot m_acceptSlot;
      ChannelClosedSlot m_channelClosedSlot;
      ServerConnectionClosedSlot m_serverConnectionClosedSlot;
      bool m_isOpen;
      bool m_isClosing;
      AcceptState<ChannelType> m_acceptState;
      std::vector<ChannelType*> m_channels;
      mutable boost::condition_variable_any m_closedCondition;

      void CloseResources();
      void WaitForClose(boost::unique_lock<boost::recursive_mutex>& lock);
      void OnChannelAccepted();
      void OnChannelClosed(ChannelType* channel);
      void OnServerConnectionClosed();
  };

  template<typename ChannelType>
  ConnectionServer<ChannelType>::ConnectionServer()
      : m_isOpen(false),
        m_isClosing(false) {}

  template<typename ChannelType>
  ConnectionServer<ChannelType>::~ConnectionServer() {
    assert(!m_isOpen && !m_isClosing);
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::Initialize(
      ServerConnection* serverConnection, const AcceptSlot& acceptSlot,
      const ChannelClosedSlot& channelClosedSlot,
      const ServerConnectionClosedSlot& serverConnectionClosedSlot) {
    assert(!m_isOpen);
    m_serverConnection.reset(serverConnection);
    m_acceptSlot = acceptSlot;
    m_channelClosedSlot = channelClosedSlot;
    m_serverConnectionClosedSlot = serverConnectionClosedSlot;
    m_acceptState.Initialize(m_serverConnection.get(),
      boost::bind(&ConnectionServer::OnChannelAccepted, this));
  }

  template<typename ChannelType>
  bool ConnectionServer<ChannelType>::IsOpen() const {
    boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
    return m_isOpen;
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::Open() {
    boost::unique_lock<boost::recursive_mutex> lock(m_mutex);
    if(m_isOpen) {
      return;
    }
    m_serverConnection->Open();
    bool isOpen;
    m_serverConnection->SetClosedSlot(boost::bind(
      &ConnectionServer::OnServerConnectionClosed, this), Store(isOpen));
    m_serverConnectionState.Initialize(isOpen);
    if(!m_serverConnectionState.IsOpen()) {
      CloseResources();
      WaitForClose(lock);
      BOOST_THROW_EXCEPTION(IO::ConnectException("Server not connected."));
    }
    m_acceptState.Accept();
    m_isOpen = true;
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::Close() {
    boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
    if(!m_isOpen) {
      return;
    }
    CloseResources();
    WaitForClose(lock);
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::RemoveChannel(ChannelType* channel) {
    boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
    Remove(m_channels, channel);
    delete channel;
    if(m_isClosing && m_channels.empty()) {
      CloseResources();
    }
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::CloseResources() {
    m_isClosing = true;
    if(m_serverConnectionState.IsOpen()) {
      m_serverConnection->Close();
      return;
    } else if(m_acceptState.GetState() ==
        Threading::Async<ChannelType*>::PENDING) {
      return;
    } else if(!m_channels.empty()) {
      for(std::vector<ChannelType*>::const_iterator i = m_channels.begin();
          i != m_channels.end(); ++i) {
        (*i)->GetConnection().Close();
      }
      return;
    }
    m_isClosing = false;
    m_isOpen = false;
    m_closedCondition.notify_one();
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::WaitForClose(
      boost::unique_lock<boost::recursive_mutex>& lock) {
    if(m_isOpen || m_isClosing) {
      m_closedCondition.wait(lock);
    }
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::OnChannelAccepted() {
    boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
    m_acceptState.UpdateState();
    if(m_acceptState.GetState() == Threading::Async<ChannelType*>::EXCEPTION) {
      if(m_isClosing) {
        CloseResources();
      }
      return;
    }
    ChannelType* channel = m_acceptState.GetChannel();
    m_acceptState.Accept();
    m_channels.push_back(channel);
    m_acceptSlot(channel);
  }

  template<typename ChannelType>
  void ConnectionServer<ChannelType>::OnServerConnectionClosed() {
    boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
    m_serverConnectionState.SetClosed();
    m_serverConnectionClosedSlot();
    if(m_isClosing) {
      CloseResources();
    }
  }
}
}

#endif // AVALON_CONNECTIONSERVER_HPP
